home *** CD-ROM | disk | FTP | other *** search
- <?xml version="1.0"?>
- <!-- ========================================================= -->
- <!-- Stylesheet for showing how to draw a simple pie chart -->
- <!-- from XML data. -->
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
- <!-- Written by Martin "Marrow" Rowlinson -->
- <!-- Marrowsoft Ltd. England -->
- <!-- With special thanks to Dimitre Novatchev for the -->
- <!-- trigonometric functions library (trig_lib.xsl). -->
- <!-- ========================================================= -->
- <xsl:stylesheet version="1.0"
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:svg="http://www.w3.org/2000/svg">
- <xsl:output method="xml" indent="yes" media-type="text/xml+svg"/>
-
- <!-- include trig functions -->
- <xsl:include href="trig_lib.xsl"/>
-
- <!-- define the size of the pie chart -->
- <xsl:param name="pie_radiusX" select="number(200)"/>
- <xsl:param name="pie_radiusY" select="$pie_radiusX"/>
- <xsl:param name="pie_explode_radiusX" select="number(20)"/>
- <xsl:param name="pie_explode_radiusY" select="$pie_explode_radiusX"/>
- <!-- define the center position of the pie chart -->
- <xsl:param name="pie_centerX" select="$pie_radiusX + 150"/>
- <xsl:param name="pie_centerY" select="$pie_radiusY + 50"/>
- <!-- define a segment to explode -->
- <!-- set to n to explode that segment (by order) -->
- <!-- 0 to explode none -->
- <!-- -1 to explode all -->
- <!-- -2 to explode 'others' (grouped segment) -->
- <xsl:param name="pie_segment_explode" select="number(0)"/>
- <!-- define the start angle of the first pie segment -->
- <xsl:param name="pie_start_angle" select="number(0)"/>
- <!-- define the threshold at which below this % a segment -->
- <!-- is not shown individually but grouped -->
- <xsl:param name="pie_lowest_show_percent" select="number(5)"/>
- <!-- pie chart title -->
- <xsl:param name="pie_title" select="'% Sales by Product ID'"/>
-
- <!-- key used for grouping the product sales by @product_id -->
- <xsl:key name="kProdSales" match="product_sales" use="@product_id"/>
-
- <!-- ========================================================= -->
- <!-- Main stylesheet template -->
- <xsl:template match="/">
- <!-- get list of distinct products -->
- <xsl:variable name="ProdSales" select="sales_summary/weekly_sales/product_sales[generate-id() = generate-id(key('kProdSales',@product_id))]"/>
- <!-- get total value of all product sales -->
- <xsl:variable name="TotalSalesValue" select="sum(sales_summary/weekly_sales/product_sales/@value)"/>
- <!-- do the svg root stuff -->
- <svg:svg id="body" viewBox="0 0 -100 {$pie_centerY + $pie_radiusY + 75}">
- <!-- call recursive template to work though the products -->
- <xsl:call-template name="do_segments">
- <xsl:with-param name="distinct_prod_sales" select="$ProdSales"/>
- <xsl:with-param name="total_sales" select="$TotalSalesValue"/>
- </xsl:call-template>
- <!-- draw the pie chart title -->
- <svg:text text-anchor="middle" font-size-adjust="+1" x="{$pie_centerX - $pie_radiusX}" y="{$pie_centerY + $pie_radiusY + 50}" dx="{2 * $pie_radiusX}">
- <xsl:value-of select="$pie_title"/>
- </svg:text>
- </svg:svg>
- </xsl:template>
-
- <!-- ========================================================= -->
- <!-- Recursive template used to draw each segment sequentially -->
- <!-- Notes: -->
- <!-- 1) the template is recursed so that some segments may be -->
- <!-- skipped where their size falls below a display min -->
- <!-- threshold. -->
- <!-- 2) a divide and conquer may be better - but it is assumed -->
- <!-- that the segments to be drawn would not be large - and -->
- <!-- to keep this demo straight forward as possible. -->
- <xsl:template name="do_segments">
- <xsl:param name="distinct_prod_sales"/>
- <xsl:param name="total_sales"/>
- <!-- params used in recursive calls -->
- <xsl:param name="total_perc_so_far" select="number(0)"/>
- <xsl:param name="_i" select="number(1)"/> <!-- used to track actual indice within node-set -->
- <xsl:param name="_i2" select="number(1)"/> <!-- only incremented when a segement is actually drawn -->
- <xsl:param name="_left_overs_perc" select="number(0)"/>
- <!-- are we handling the left overs or an actual product -->
- <xsl:choose>
- <xsl:when test="$distinct_prod_sales[$_i]">
- <!-- calc total of sales for this product id -->
- <xsl:variable name="this_prod_sales" select="sum(key('kProdSales',$distinct_prod_sales[$_i]/@product_id)/@value)"/>
- <!-- calc the percentage of this product sales over total -->
- <xsl:variable name="perc_of_total" select="$this_prod_sales div $total_sales"/>
- <!-- actual product - decide whether it is below draw segment threshold -->
- <xsl:choose>
- <xsl:when test="$perc_of_total < ($pie_lowest_show_percent div 100)">
- <!-- skip it and show it bunched with rest of below thresholds -->
- <xsl:call-template name="do_segments">
- <xsl:with-param name="distinct_prod_sales" select="$distinct_prod_sales"/>
- <xsl:with-param name="total_sales" select="$total_sales"/>
- <xsl:with-param name="total_perc_so_far" select="$total_perc_so_far"/>
- <xsl:with-param name="_i" select="$_i + 1"/>
- <xsl:with-param name="_i2" select="$_i2"/>
- <xsl:with-param name="_left_overs_perc" select="$_left_overs_perc + $perc_of_total"/>
- </xsl:call-template>
- </xsl:when>
- <xsl:otherwise>
- <!-- draw it -->
- <xsl:call-template name="draw_pie_segment">
- <xsl:with-param name="angle" select="360 * $perc_of_total"/>
- <xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
- <xsl:with-param name="label" select="concat($distinct_prod_sales[$_i]/@product_id,' (',format-number($perc_of_total * 100,'0.0'),'%)')"/>
- <xsl:with-param name="explode" select="($pie_segment_explode = $_i2) or ($pie_segment_explode = -1)"/>
- <xsl:with-param name="fill_color">
- <xsl:call-template name="calc_color">
- <xsl:with-param name="rel_position" select="$_i2"/>
- </xsl:call-template>
- </xsl:with-param>
- <xsl:with-param name="radiusX" select="$pie_radiusX"/>
- <xsl:with-param name="radiusY" select="$pie_radiusY"/>
- <xsl:with-param name="centerX" select="$pie_centerX"/>
- <xsl:with-param name="centerY" select="$pie_centerY"/>
- <xsl:with-param name="explode_radiusX" select="$pie_explode_radiusX"/>
- <xsl:with-param name="explode_radiusY" select="$pie_explode_radiusY"/>
- </xsl:call-template>
- <!-- and do the next segment (or any left overs) -->
- <xsl:call-template name="do_segments">
- <xsl:with-param name="distinct_prod_sales" select="$distinct_prod_sales"/>
- <xsl:with-param name="total_sales" select="$total_sales"/>
- <xsl:with-param name="total_perc_so_far" select="$total_perc_so_far + $perc_of_total"/>
- <xsl:with-param name="_i" select="$_i + 1"/>
- <xsl:with-param name="_i2" select="$_i2 + 1"/>
- <xsl:with-param name="_left_overs_perc" select="$_left_overs_perc"/>
- </xsl:call-template>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:when>
- <xsl:when test="$_left_overs_perc > 0">
- <!-- left overs - with some to actually do -->
- <xsl:call-template name="draw_pie_segment">
- <xsl:with-param name="angle" select="360 * $_left_overs_perc"/>
- <xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
- <xsl:with-param name="label" select="concat('Others (',format-number($_left_overs_perc * 100,'0.0'),'%)')"/>
- <xsl:with-param name="explode" select="($pie_segment_explode < 0)"/>
- <xsl:with-param name="fill_color">
- <xsl:call-template name="calc_color">
- <xsl:with-param name="rel_position" select="$_i2"/>
- </xsl:call-template>
- </xsl:with-param>
- <xsl:with-param name="radiusX" select="$pie_radiusX"/>
- <xsl:with-param name="radiusY" select="$pie_radiusY"/>
- <xsl:with-param name="centerX" select="$pie_centerX"/>
- <xsl:with-param name="centerY" select="$pie_centerY"/>
- <xsl:with-param name="explode_radiusX" select="$pie_explode_radiusX"/>
- <xsl:with-param name="explode_radiusY" select="$pie_explode_radiusY"/>
- </xsl:call-template>
- </xsl:when>
- </xsl:choose>
- </xsl:template>
-
- <!-- ========================================================= -->
- <!-- Template for drawing a single pie chart segment -->
- <!-- Nnote: All info that this template requires for drawing -->
- <!-- the segment is passed in as a parameter. Thus, -->
- <!-- this template is re-usable to some extent. -->
- <xsl:template name="draw_pie_segment">
- <xsl:param name="angle"/>
- <xsl:param name="prev_angle"/>
- <xsl:param name="label"/>
- <xsl:param name="explode" select="false()"/>
- <xsl:param name="fill_color" select="'red'"/>
- <xsl:param name="line_color" select="'black'"/>
- <xsl:param name="radiusX" select="number(200)"/>
- <xsl:param name="radiusY" select="number(200)"/>
- <xsl:param name="centerX" select="$radiusX + 50"/>
- <xsl:param name="centerY" select="$radiusY + 50"/>
- <xsl:param name="explode_radiusX" select="number(20)"/>
- <xsl:param name="explode_radiusY" select="number(20)"/>
- <!-- calc the full angle -->
- <xsl:variable name="full_angle" select="$prev_angle + $angle"/>
- <!-- calc the half angle - for use with labels and exploded segments -->
- <xsl:variable name="half_angle" select="$prev_angle + ($angle div 2)"/>
- <!-- calc things that change according to explode segment status -->
- <xsl:variable name="X0">
- <xsl:choose>
- <xsl:when test="$explode">
- <xsl:variable name="sinX">
- <xsl:call-template name="sin">
- <xsl:with-param name="pX" select="$half_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="$centerX + ($sinX * $explode_radiusX)"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$centerX"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="Y0">
- <xsl:choose>
- <xsl:when test="$explode">
- <xsl:variable name="cosX">
- <xsl:call-template name="cos">
- <xsl:with-param name="pX" select="$half_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="$centerY + (0 - ($cosX * $explode_radiusY))"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$centerY"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <!-- calc the co-ordinates of the important points of the segment -->
- <xsl:variable name="X1">
- <xsl:variable name="sinX">
- <xsl:call-template name="sin">
- <xsl:with-param name="pX" select="$prev_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="($sinX * $radiusX)"/>
- </xsl:variable>
- <xsl:variable name="Y1">
- <xsl:variable name="cosX">
- <xsl:call-template name="cos">
- <xsl:with-param name="pX" select="$prev_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="0 - ($cosX * $radiusY)"/>
- </xsl:variable>
- <xsl:variable name="X2">
- <xsl:variable name="sinX">
- <xsl:call-template name="sin">
- <xsl:with-param name="pX" select="$full_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="($sinX * $radiusX) - $X1"/>
- </xsl:variable>
- <xsl:variable name="Y2">
- <xsl:variable name="cosX">
- <xsl:call-template name="cos">
- <xsl:with-param name="pX" select="$full_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="(0 - ($cosX * $radiusY)) - $Y1"/>
- </xsl:variable>
- <!-- for the label - work out the quadrant in which it will appear -->
- <xsl:variable name="label_quandrant" select="floor(($half_angle mod 360) div 90)"/>
- <xsl:variable name="textXalign">
- <xsl:choose>
- <xsl:when test="$label_quandrant < 2">
- <xsl:text>start</xsl:text>
- </xsl:when>
- <xsl:otherwise>
- <xsl:text>end</xsl:text>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="textDY">
- <xsl:choose>
- <xsl:when test="($label_quandrant = 0) or ($label_quandrant = 3)">
- <xsl:text>-10</xsl:text>
- </xsl:when>
- <xsl:otherwise>
- <xsl:text>15</xsl:text>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <!-- calc X and Y posn for the label -->
- <xsl:variable name="XTxt">
- <xsl:variable name="sinX">
- <xsl:call-template name="sin">
- <xsl:with-param name="pX" select="$half_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="$X0 + ($sinX * ($radiusX + 10))"/>
- </xsl:variable>
- <xsl:variable name="YTxt">
- <xsl:variable name="cosX">
- <xsl:call-template name="cos">
- <xsl:with-param name="pX" select="$half_angle"/>
- <xsl:with-param name="pUnit" select="'deg'"/>
- </xsl:call-template>
- </xsl:variable>
- <xsl:value-of select="$Y0 + (0 - ($cosX * $radiusY))"/>
- </xsl:variable>
- <!-- do the actual svg -->
- <svg:g id="{$label}">
- <!-- draw the segment itself -->
- <svg:path fill="{$fill_color}" stroke="{$line_color}" d="M{$X0},{$Y0} l{$X1},{$Y1} a{$radiusX},{$radiusY} 0 0 1 {$X2},{$Y2} L{$X0},{$Y0}"/>
- <!-- draw the segment label -->
- <svg:text text-anchor="{$textXalign}" x="{$XTxt}" y="{$YTxt}" dy="{$textDY}">
- <xsl:value-of select="$label"/>
- </svg:text>
- </svg:g>
- </xsl:template>
-
- <!-- ========================================================= -->
- <!-- Called template for calculating colors -->
- <!-- this is a crude way of giving each segment a different -->
- <!-- color - calculated according to a relative position -->
- <xsl:template name="calc_color">
- <xsl:param name="rel_position" select="number(0)"/>
- <xsl:variable name="color_lightnesses" select="'F0D0B09070503010E0C0A08060402000'"/>
- <xsl:text>#</xsl:text>
- <!-- red part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 0">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- <!-- blue part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 1">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- <!-- green part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 2">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:template>
-
- </xsl:stylesheet>